package org.mspring.mlog.biz;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import org.mspring.mlog.service.ArticleEntityService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @author Gao Youbo
 * @since 2015-01-20 13:07
 */
@Component
public class ArticleViewCountService {

    private static final Logger LOG = LoggerFactory.getLogger(ArticleViewCountService.class);
    private static final int THREAD_POOL_SIZE = 3;

    private ArticleEntityService articleEntityService;

    private ReadWriteLock lock = new ReentrantReadWriteLock();
    private ExecutorService executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
    private BlockingQueue<ViewCountTask> tasks = new LinkedBlockingQueue<>();
    private volatile boolean run = true;
    private LoadingCache<Long, Integer> viewCountCache = CacheBuilder.newBuilder()
            .build(new CacheLoader<Long, Integer>() {
                @Override
                public Integer load(Long articleId) throws Exception {
                    return articleEntityService.getArticleViewCount(articleId);
                }
            });

    @PostConstruct
    void init() {
        for (int i = 0; i < THREAD_POOL_SIZE; i++) {
            executorService.submit(new TaskConsumer());
        }
    }

    @PreDestroy
    void destory() {
        run = false;
        executorService.shutdownNow();
    }

    public void increment(long articleId) {
        tasks.add(new ViewCountTask(articleId));
    }

    public int getViewCount(long articleId) {
        lock.readLock().lock();
        try {
            Integer viewCount = viewCountCache.get(articleId);
            return viewCount == null ? 0 : viewCount.intValue();
        } catch (ExecutionException e) {
            LOG.error("获取文章ViewCount失败,id=" + articleId);
            return 0;
        } finally {
            lock.readLock().unlock();
        }
    }

    class ViewCountTask {
        private long articleId;

        public ViewCountTask(long articleId) {
            this.articleId = articleId;
        }

        public long getArticleId() {
            return articleId;
        }

        public void setArticleId(long articleId) {
            this.articleId = articleId;
        }
    }

    class TaskConsumer implements Runnable {
        @Override
        public void run() {
            try {
                ViewCountTask task;
                while (run && (task = tasks.take()) != null) {
                    int viewCount = getViewCount(task.getArticleId());
                    lock.writeLock().lock();
                    try {
                        viewCount++;
                        articleEntityService.updateArticleViewCount(task.getArticleId(), viewCount);
                        viewCountCache.put(task.getArticleId(), viewCount);
                    } finally {
                        lock.writeLock().unlock();
                    }
                }
            } catch (InterruptedException e) {
                LOG.error(null, e);
            }
        }
    }

    @Autowired
    public void setArticleEntityService(ArticleEntityService articleEntityService) {
        this.articleEntityService = articleEntityService;
    }
}
